﻿// Shader-Based-Image-Processing (SBIP)
// http://code.google.com/p/sbip/
//
// Copyright © Frank Nagl, 2009-2011
// admin@franknagl.de
//
namespace SBIP.Filter
{
    using System;
    using System.Reflection;
    using System.Resources;
    using Helper;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Content.Pipeline;
    using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
    using Microsoft.Xna.Framework.Content.Pipeline.Processors;
    using Microsoft.Xna.Framework.Graphics;

    /// <summary>
    /// Base class for shader-based image processing filter.
    /// </summary>
    /// <remarks>
    /// Provides a method to init and configure a shader for a specified
    /// image processing filter.
    /// </remarks>
    public abstract class BaseFilter
    {
        #region Constant variables        
        /// <summary>Message for ArgumentException, thrown by defining a 
        /// property before setting <see cref="Processor.Filter"/>.</summary>
        protected const string InitMsg = " can be defined primaly after setting property Processor.Filter.";

        /// <summary>Message for <see cref="NotImplementedException"/>, thrown by initializing a 
        /// HiDef filter with XNA's Reach graphics profile.</summary>
        protected const string NotImplementedMsg = " can only be used with XNA's HiDef graphics profile. " + 
            "The current graphics adapter does not support this profile.";

        /// <summary>When HLSL rendering the texture coordinates have a little shifting. This value corrects this.
        /// Note: Unit in pixels.</summary>
        internal const byte Adjustment = 4;
        #endregion Constant variables

        #region Private members
        /// <summary>Pointer to actual texture.</summary>
        private Texture2D texture;
        /// <summary>The render quad.</summary>
        private Quad quad;
        /// <summary>Reference to XNA graphics device. Note: Only using it in PostInit().</summary>
        private GraphicsDevice graphics;
        private string technique;
        #endregion Private members

        #region Protected and internal properties
        /// <summary>Flag for XNA graphics profiles. True, if graphics adapter supports 
        /// XNA 4.0 GraphicsProfile.HiDef, otherwise it supports GraphicsProfile.Reach.
        /// Note: Flag from <see cref="Processor.HighDef"/>.</summary>
        protected bool HiDef { get; private set; }

        /// <summary>The specified shader effect.</summary>
        protected Effect Effect { get; set; }

        /// <summary>The fx effect file, located in project's resources file.</summary>
        protected string EffectFile { get; set; }

        /// <summary>Actual texture.</summary>
        internal Texture2D Texture
        {
            set
            {
                if (Effect == null)
                    throw new ArgumentException("Texture" + InitMsg);
                texture = value;
                Effect.Parameters["ImageTexture"].SetValue(value);
            }
        }
        #endregion Protected and internal properties

        #region Public properties
        /// <summary>Determines, whether a filter is real-time capable or not.
        /// Note: Default: true. Filter will not be analyzed and this 
        /// flag will not be set automatically. </summary>
        public bool RealtimeCapable { get; protected set; }

        /// <summary>
        /// The technique of the  effect. If no technique was specified, 
        /// the first technique of effect' shader code is used.
        /// </summary>
        public string Technique
        {
            get { return technique; }
            set 
            { 
                technique = value;
                if (Effect == null)
                    throw new ArgumentException("Technique" + InitMsg);
                Effect.CurrentTechnique = Effect.Techniques[value];
            }
        }
        #endregion Public properties

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseFilter"/> class.
        /// </summary>
        /// <param name="effectFile">The  effect file, located in project's resources file.</param>
        protected BaseFilter(string effectFile) 
        {
            EffectFile = effectFile;
            RealtimeCapable = true;
        }

        #region Internal methods

        /// <summary>
        /// Inits the specified effect.
        /// </summary>
        /// <param name="tex">The original texture to process.</param>
        /// <param name="graphicsDevice">The XNA graphics device.</param>
        /// <param name="hiDef">Flag for XNA graphics profiles. True, if graphics adapter supports XNA 4.0 
        /// GraphicsProfile.HiDef, otherwise it supports GraphicsProfile.Reach.</param>
        internal void Init(Texture2D tex, GraphicsDevice graphicsDevice, bool hiDef)
        {
            HiDef = hiDef;
            CheckHighDef();

            graphics = graphicsDevice;
            //if (quad == null)
            quad = new Quad(tex.Width, tex.Height);

            Vector2 delta = 
                new Vector2(1.0f / tex.Width, 1.0f / tex.Height);

            EffectContent effectSource;
            string s;

            if (EffectFile.EndsWith(".fx"))
            {
                s = IOFile.ReadFile(EffectFile);
                effectSource = new EffectContent
                {
                    Identity = new ContentIdentity { SourceFilename = EffectFile },
                    EffectCode = s
                };
            }
            else
            {
                // Load the effect from Resources.resx file.
                ResourceManager resource =
                    new ResourceManager("SBIP.Resource",
                    Assembly.GetExecutingAssembly());
                s = resource.GetString(EffectFile);
                // ''''''''''''''''''''''''
                effectSource = new EffectContent
                {
                    Identity = new ContentIdentity { SourceFilename = EffectFile + ".fx" },
                    EffectCode = s
                };                
            }

            EffectProcessor processor = new EffectProcessor();
            CompiledEffectContent compiledEffect = processor.Process(effectSource, new Context());
            Effect = new Effect(graphicsDevice, compiledEffect.GetEffectCode());

            technique = Effect.CurrentTechnique.Name;   
            //effect.Parameters["ImageTexture"].SetValue(tex);
            Texture = tex;
            Effect.Parameters["Delta"].SetValue(delta);
            Effect.Parameters["Adjustment"].SetValue(Adjustment);

            PostInit();
        }

        /// <summary>Renders the specified effect.</summary>
        internal virtual void RenderEffect()
        {
            foreach (EffectPass pass in Effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                graphics.DrawUserIndexedPrimitives(
                    PrimitiveType.TriangleList,
                    quad.Vertices,
                    0,
                    4,
                    quad.Indexes,
                    0,
                    2);
            }    
        }

        /// <summary>Renders the specified effect into a texture.</summary>
        /// <returns>The rendered texture.</returns>
        internal virtual Texture2D RenderEffectToTexture()
        {
            RTT rtt = new RTT(graphics);
            return rtt.Filter(texture, Effect, technique, true);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        internal virtual void Dispose() 
        {
            Effect.Dispose();
        }

        #endregion Internal methods

        #region Protected methods

        /// <summary>
        /// Has to be implemented from filters, which need (or need to adapt when) 
        /// XNA's HiDef graphics profile.
        /// </summary>
        protected virtual void CheckHighDef() { }

        /// <summary>
        /// Loads an external effect and returns it.
        /// </summary>
        /// <param name="externalEffectFile">The name of external effect file in the resources.</param>
        /// <returns>The loaded external effect.</returns>
        protected Effect GetExternalEffect(string externalEffectFile)
        {
            // Load the effect from Resources.resx file.
            ResourceManager resource =
                new ResourceManager("SBIP.Resource",
                Assembly.GetExecutingAssembly());
            string s = resource.GetString(externalEffectFile);
            // ''''''''''''''''''''''''
            EffectContent effectSource = new EffectContent
            {
                Identity = new ContentIdentity { SourceFilename = externalEffectFile + ".fx" },
                EffectCode = s
            };

            EffectProcessor processor = new EffectProcessor();
            CompiledEffectContent compiledEffect = processor.Process(effectSource, new Context());
            Effect e = new Effect(graphics, compiledEffect.GetEffectCode());
            return e;
        }

        /// <summary>
        /// Provides inidvidual filter initialization after graphics device 
        /// and effect initialization.
        /// </summary>
        protected virtual void PostInit() { }

        /// <summary>
        /// Renders the specified effect into a texture.
        /// </summary>
        /// <param name="pEffect">The effect.</param>
        /// <param name="pTechnique">The technique to use.</param>
        /// <returns>The rendered texture.</returns>
        protected Texture2D RenderExternalEffectToTexture(
            Effect pEffect, 
            string pTechnique)
        {
            RTT rtt = new RTT(graphics);
            Texture2D tex = rtt.Filter(texture, pEffect, pTechnique, true);
            return tex;
        }

        /// <summary>
        /// Renders the specified effect into a texture.
        /// </summary>
        /// <param name="pTexture">The external texture to process.</param>
        /// <returns>The rendered texture.</returns>
        protected Texture2D RenderEffectWithExternalTextureToTexture(Texture2D pTexture)
        {
            RTT rtt = new RTT(graphics);
            return rtt.Filter(pTexture, Effect, technique, true);
        }

        /// <summary>
        /// Renders the specified effect into a texture.
        /// </summary>
        /// <param name="pTexture">The external texture to process.</param>
        /// <param name="pEffect">The effect.</param>
        /// <param name="pTechnique">The technique to use.</param>
        /// <param name="format">The format of the RenderTarget (destination texture).</param>
        /// <returns>The rendered texture.</returns>
        protected Texture2D RenderExternalTextureWithExternalEffectToTexture(
            Texture2D pTexture, 
            Effect pEffect, 
            string pTechnique, 
            SurfaceFormat format)
        {
            RTT rtt = new RTT(graphics);
            rtt.PrepareRTT(pTexture, format);
            Texture2D tex = rtt.Filter(pTexture, pEffect, pTechnique, false);
            return tex;
        }

        /// <summary>
        /// Sets the passed <paramref name="bitmap"/> as the <see cref="Texture"/>.
        /// </summary>
        /// <param name="bitmap">The bitmap to set as texture.</param>
        protected void SetBitmapAsTexture(System.Drawing.Bitmap bitmap)
        {
            if (bitmap != null)
            {
                if (texture != null)
                {
                    texture.Dispose();
                    texture = null;
                }
                BitmapToTexture.ToTexture(bitmap, graphics, out texture);
                if (Effect == null)
                    throw new ArgumentException("Texture" + InitMsg);
                Effect.Parameters["ImageTexture"].SetValue(texture);
            }
        }

        #endregion Protected methods
    }
}
